#include <TimerOne.h>


/* =====================================
 * PWM part (not using Ardu functions)
 * =====================================*/
#define PULSE_TIME  120L  // us
#define MAX_PWM_DUTY  100 // resolution
#define DEFAULT_PWM_PIN 13  


static struct {
  int pin;
  int state;      // 1.state of level; 2.also used to make state toggle easier
  unsigned duration[2];  // time for low and high, respectively
} g_pwmInfo;


void PWM_Begin() {
  g_pwmInfo.pin = DEFAULT_PWM_PIN;
  g_pwmInfo.state = 0;
  Timer1.initialize(PULSE_TIME);
  Timer1.attachInterrupt(PWM_Routine);
}


void PWM_Set(int16_t pin, uint16_t duty){
  g_pwmInfo.pin = pin;
  
  // cannot output pwm at such condition
  if      (duty <= 0)            {duty = 1;}
  else if (duty >= MAX_PWM_DUTY) {duty = MAX_PWM_DUTY - 1;}

  // === normal condition
  uint16_t complementary_duration;
  if (duty < (MAX_PWM_DUTY / 2)) {
    complementary_duration = (uint32_t)PULSE_TIME * (MAX_PWM_DUTY - duty) / duty;
    g_pwmInfo.duration[0] = complementary_duration;   // duration at low(0)
    g_pwmInfo.duration[1] = PULSE_TIME;               // duration at high(1)
  }
  else {
    complementary_duration = (uint32_t)PULSE_TIME * duty / (MAX_PWM_DUTY - duty);
    g_pwmInfo.duration[1] = complementary_duration;   // duration at high(1)
    g_pwmInfo.duration[0] = PULSE_TIME;               // duration at low(0)
  }
}

void PWM_Routine(){
  // toggle the level and switch duration for this level
  noInterrupts();
  g_pwmInfo.state = !g_pwmInfo.state;
  Timer1.setPeriod(g_pwmInfo.duration[g_pwmInfo.state]);  // duration at such a state
  g_pwmInfo.state ? digitalWrite(g_pwmInfo.pin, HIGH) 
                  : digitalWrite(g_pwmInfo.pin, LOW);     // level at such a state
  interrupts();
}


void printPWMSetting() {
  Serial.println("\n===== PWM Info =====");
  Serial.print("pin: "); Serial.println(g_pwmInfo.pin);
  Serial.print("state: "); Serial.println(g_pwmInfo.state);
  Serial.print("dura0: "); Serial.println(g_pwmInfo.duration[0]);
  Serial.print("dura1: "); Serial.println(g_pwmInfo.duration[1]);
}


void setup() {
  Serial.begin(115200);
  pinMode(2, OUTPUT);
  
  PWM_Begin();
  PWM_Set(2, 10);
}


String inStr = "";
void loop() {
  inStr = "";
  while (Serial.available() > 0) {
    int inChar = Serial.read();
    if ('\n' == inChar) {
      int duty = inStr.toInt();
      Serial.print("got: "); Serial.println(inStr);
      Serial.print("converted: "); Serial.println(duty);
      PWM_Set(2, duty);
    }
    inStr += (char)inChar;
  }
  
  printPWMSetting();  
  delay(2000);
}
